home *** CD-ROM | disk | FTP | other *** search
- /* Functions for displaying a progress dialog (as described in IM-VI, p2-25)
- and handling events during long execution of lengthy algorithms.
-
- 94/01/17 aih
- - When in the background, uses a value of 5 ticks for RUN_TICKS
- instead of a value of 0 ticks. This makes background operation
- a bit speedier without overly slowing down the foreground application.
-
- 93/12/16 aih
- - progress dialog's position is saved/restored to/from preferences file
-
- 93/11/03 aih
- - converted to use a handle instead of a pointer
-
- 93/10/27 aih
- - added functions for reseting the dialog and setting the prompt
-
- 93/10/22 aih
- - removed ProgressInit, global variables, and saving/restoring progress
- dialog's position to a global variable
-
- 93/10/20 aih
- - added support for modeless dialogs
-
- 93/10/13 AIH
- - Added special meaning to value of 0 for idle parameter
- - Added check for command-period even if the progress dialog isn't
- open yet
- - Added canceled field to progress structure
-
- 93/03/19 AIH
- - Changed ProgressLibBegin to ProgressInit
-
- 93/03/17 AIH
- - Added user specifiable dialog items
-
- 93/03/13 AIH
- - Changed library name from "EventPeriodicLib" to "ProgressLib"
-
- 93/03/06 AIH
- - Fixed cursor spinning interval
-
- 92/02/29 AIH
- - Made into a separate file. */
-
- #include <stdarg.h>
- #include <stdio.h>
- #include <string.h>
- #include <SysEqu.h>
- #include "DialogLib.h"
- #include "DialogModalLib.h"
- #include "DrawLib.h"
- #include "EventLib.h"
- #include "KeyLib.h"
- #include "MathLib.h"
- #include "MemoryLib.h"
- #include "PreferencesLib.h"
- #include "ProgressLib.h"
- #include "RectangleLib.h"
- #include "ResourceLib.h"
- #include "ResourceConstantsLib.h"
- #include "StringLib.h"
- #include "TimeLib.h"
- #include "WindowLib.h"
-
- #define Ticks() (*(long *) Ticks) /* faster than calling TickCount() */
-
- /* While the application is doing some lengthy operation it should
- periodically call EventAvail, GNE, or WNE to find out if there are any events it
- should handle by entering the event loop and to allow other applications
- to receive CPU time. The application should also display a progress dialog and
- should spin a cursor. The following intervals have been determined
- empirically to be approproriate for each of these actions. They should
- give enough time to other applications without unduly slowing down the
- current application. Notice that when the application is in the background
- it gives significantly more time to other applications, since presumabely
- the user cares more about the response of the foreground application than
- the time it takes for an application to execute in the background. */
- #define EVENT_TICKS (30) /* interval to check for events */
- #define CURS_TICKS (30) /* interval to spin the cursor */
- #define UPDT_TICKS (60) /* interval to update dialog */
- #define START_TICKS (120) /* interval before showing dialog */
- #define RUN_TICKS (gInBackground ? 5 : 30) /* interval before doing any of the above */
-
- /* true if valid progress */
- Boolean ProgressValid(ProgressHandle progress)
- {
- return(HandleValidSize(progress, sizeof(ProgressType)));
- }
-
- /* Update function for progress indicator in progress event loop's
- dialog. This draws a black bar which advances towards the right,
- in a manner very similar to the way that Finder 6.0 indicates
- how many files it has copied. */
- void ProgressUpdate(ProgressHandle progress)
- {
- Rect box;
- Rect fill;
- GrafPtr port;
- PenState pen;
-
- require(ProgressValid(progress));
- if ((**progress).dlg) {
- GetPort(&port);
- SetPort((**progress).dlg);
- GetPenState(&pen);
- PenNormal();
- DlgBox((**progress).dlg, (**progress).useritm, &box);
- FrameRect(&box);
- fill = box;
- if ((**progress).maxcnt > 0 && (**progress).maxcnt > (**progress).count) {
- fill.right = box.left +
- ((float) (**progress).count / (**progress).maxcnt) * RectWidth(&box);
- }
- if (RectWidth(&fill) > 1) {
- InsetRect(&fill, 1, 1);
- PaintRect(&fill);
- }
- fill.top = box.top;
- fill.left = fill.right;
- fill.bottom = box.bottom;
- fill.right = box.right;
- InsetRect(&fill, 1, 1);
- EraseRect(&fill);
- SetPenState(&pen);
- SetPort(port);
- (**progress).lastUpdate = Ticks();
- }
- ensure(ProgressValid(progress));
- }
-
- /* Open the progress dialog. If 'modal' is false then a movable modal dialog
- is created (actually, whatever's specified in the 'DLOG' resource),
- while if 'modal' is true then a noGrowDocProc dialog is created. */
- void ProgressOpenDialog(ProgressHandle progress, Boolean modal)
- {
- volatile DialogTHndl template = NULL;
- DialogPtr dlg = NULL;
-
- require(ProgressValid(progress));
- require(! (**progress).dlg);
- TRY {
-
- /* force dialog to be non-modal if necessary */
- (**progress).modal = modal;
- if (! (**progress).modal) {
- template = (DialogTHndl) ResGet('DLOG', RLD_BUSY);
- (void) HandleNoPurge(template);
- (**template).procID = noGrowDocProc;
- }
-
- /* create and position the dialog */
- dlg = DlgBegin(RLD_BUSY);
- (**progress).dlg = dlg;
- WinRegister((**progress).dlg, progress, ProgressEventTable());
- DlgPosition((**progress).dlg);
- check(! (**progress).appdlg);
- WinZoomRestore((**progress).dlg, PrefsFile(), PREFS_PROGRESS_POSITION);
-
- /* force check for update and activate events for dialog */
- (**progress).lastRun = 0;
-
- /* set the prompt if one was provided */
- if (*(**progress).str) {
- Str255 str;
- BlockMove((**progress).str, str, *(**progress).str + 1);
- DlgTextSet((**progress).dlg, (**progress).textitm, p2cstr(str));
- }
- } CLEANUP {
- if (template) ResRelease((Handle) template);
- } CATCH {
- DlgEnd((**progress).dlg);
- (**progress).dlg = NULL;
- } ENDTRY;
- ensure((**progress).modal == WinIsModal((**progress).dlg));
- ensure(ProgressValid(progress));
- }
-
- /* set the cursor to the arrow cursor in response to user events */
- static void AdjustCursor(ProgressHandle progress, EventRecord *event)
- {
- if (event->what == mouseDown ||
- event->what == keyDown ||
- event->what == autoKey)
- {
- (**progress).cursorInterval = START_TICKS;
- DrawCursor(arrowCursor);
- }
- }
-
- /* The following functions are called by ProgressRun. See its description
- for some details. */
-
- /* process events */
- static void ProgressEvents(ProgressHandle progress)
- {
- EventRecord event;
- int mask = everyEvent;
-
- /* Run the event loop if there are any pending events. If the event
- is a user event and the dialog is (or would be) modal, then the progress
- dialog must exist, since otherwise the menus and other windows might not
- be properly disabled; the simplest solution is to handle update and
- activate events if the dialog doesn't exist, since these events are
- not generated directly by the user, and they have a high priority which
- could lock out other applications from receiving CPU time. */
- if (! gInBackground && Ticks() - (**progress).lastEvent <= EVENT_TICKS)
- return;
- if (! (**progress).dlg)
- mask = keyDownMask + updateMask + activMask;
- if (EventAvail(mask, &event)) {
- while (EventGet(mask, &event, 0, NULL)) {
- if (! (**progress).dlg && event.what == keyDown && KeyCancel(&event)) {
- (**progress).canceled = true;
- break;
- }
- AdjustCursor(progress, &event);
- EventExecute(&event);
- }
- /* check for cancel */
- if ((**progress).dlg && DlgClicked((**progress).dlg) == PROGRESS_STOP)
- (**progress).canceled = true;
- if ((**progress).canceled)
- FailOSErr(userCanceledErr);
- }
- (**progress).lastEvent = Ticks();
- }
-
- /* spin the cursor */
- static void ProgressSpin(ProgressHandle progress)
- {
- if (! gInBackground && Ticks() - (**progress).lastCursor > (**progress).cursorInterval) {
- DrawCursor((**progress).cursorIndex + RLCURS_BUSY);
- (**progress).cursorIndex = ((**progress).cursorIndex + 1) % RLCURS_BUSY_COUNT;
- (**progress).cursorInterval = CURS_TICKS;
- (**progress).lastCursor = Ticks();
- }
- }
-
- /* show the dialog and update the progress indicator */
- static void ProgressShow(ProgressHandle progress)
- {
- require(ProgressValid(progress));
- if ((**progress).visible) {
- /* periodically update dialog */
- if (Ticks() - (**progress).lastUpdate > UPDT_TICKS)
- ProgressUpdate(progress);
- }
- else if (Ticks() - (**progress).start > START_TICKS) {
- /* create or show dialog if operation is taking longer than a few seconds */
- ProgressUpdate(progress);
- if ((**progress).dlg)
- WinSelect((**progress).dlg);
- else
- ProgressOpenDialog(progress, true);
- WinShow((**progress).dlg);
- (**progress).visible = true;
- }
- }
-
- /* Call this when once every time through a portion of a lengthy operation.
- After a minimum number of ticks have elapsed since the progress event
- loop was initialized a dialog (default is movable modal) is displayed
- asking the user to wait (the application must supply a dialog ID when
- initializing the event library). The 'done' parameter is used to draw
- the progress bar and should be the amount of work done so far, or -1 if
- every call to this function counts as one unit of work, or 0 to just
- process events without changing the indicator. The cursor
- will be spinned about twice a second and EventAvail is called periodically
- to give other applications time. Any pending events are handled as
- soon as EventAvail reports them. EventAvail is used since it is more
- efficient than GetNextEvent, which in turn would be more efficient
- than WaitNextEvent. */
- void ProgressRun(ProgressHandle progress, long done)
- {
- require(ProgressValid(progress));
- if (done == -1L)
- (**progress).count++;
- else if (done > 0)
- (**progress).count = done;
- if (Ticks() - (**progress).lastRun > RUN_TICKS) {
- ProgressEvents(progress);
- ProgressSpin(progress);
- ProgressShow(progress);
- (**progress).lastRun = Ticks();
- }
- ensure(ProgressValid(progress));
- }
-
- /* set the string displayed in the progress dialog */
- void ProgressPrompt(ProgressHandle progress, const char *fmt, ...)
- {
- va_list ap;
- CStr255 str;
-
- require(ProgressValid(progress));
- va_start(ap, fmt);
- vsprintf(str, fmt, ap);
- va_end(ap);
- if ((**progress).dlg)
- DlgTextSet((**progress).dlg, PROGRESS_TEXT, str);
- c2pstr(str);
- BlockMove(str, (**progress).str, *str + 1);
- ensure(ProgressValid(progress));
- }
-
- /* Reset the progress dialog. Useful when running an operation after a
- previous operation and when you don't want to close and then reopen
- the progress dialog. */
- void ProgressReset(ProgressHandle progress, size_t max)
- {
- require(ProgressValid(progress));
- (**progress).count = 0;
- (**progress).maxcnt = max;
- (**progress).canceled = false;
- (**progress).lastRun = 0;
- (**progress).lastEvent = 0;
- (**progress).lastUpdate = 0;
- (**progress).lastCursor = 0;
- if ((**progress).dlg)
- ProgressUpdate(progress);
- ensure(ProgressValid(progress));
- }
-
- /* Call this before calling ProgressRun. The 'max'
- parameter should be your best estimate of the number of times
- ProgressRun will be called. A non-zero value for 'max' will cause
- a black progress bar to be drawn in the progress event loop's dialog.
- The 'dlg' parameter should be a dialog which will be displayed while
- the application is executing its progress event loop, or NULL to use
- the default dialog. Item number 1 in the dialog should be a user item
- which will be used to display the progress bar. The 'str' parameter
- should contain a string to display in item 3 (a statText item)
- of the dialog, or NULL if no string should be used. The 'useritm'
- and 'textitm' fields should contain the item numbers of items
- to display the progress bar and prompt string; if they're 0 then
- the default items are used. */
- ProgressHandle ProgressBegin(size_t max, CStr255 str, DialogPtr dlg,
- short useritm, short textitm)
- {
- ProgressHandle progress = NULL;
-
- require(! dlg || DlgValid(dlg));
- require(! str || StrValid(str, sizeof(CStr255)));
- TRY {
- /* initialize variables */
- progress = HandleBeginClear(sizeof(ProgressType));
- (**progress).cursorInterval = CURS_TICKS;
- (**progress).start = Ticks();
- (**progress).maxcnt = max;
- (**progress).dlg = dlg;
- (**progress).appdlg = (dlg != NULL);
- (**progress).useritm = PROGRESS_USER;
- (**progress).textitm = PROGRESS_TEXT;
- if (useritm) (**progress).useritm = useritm;
- if (textitm) (**progress).textitm = textitm;
- if (str) ProgressPrompt(progress, str);
- if (! dlg || WinIsModal(dlg)) (**progress).modal = true;
- if (dlg) WinRegister(dlg, progress, ProgressEventTable());
- } CATCH {
- ProgressEnd(progress);
- } ENDTRY;
- ensure(ProgressValid(progress));
- return(progress);
- }
-
- /* Cleanup after a progress event loop by disposing of the progress dialog
- (if it was created by the event library) and reseting all the variables.
- You must call this function when you've finished with the progress event
- loop. */
- void ProgressEnd(ProgressHandle progress)
- {
- if (progress) {
- if ((**progress).dlg) {
- WinUnregister((**progress).dlg, progress);
- if (! (**progress).appdlg) {
- WinZoomSave((**progress).dlg, PrefsFile(), PREFS_PROGRESS_POSITION);
- DlgEnd((**progress).dlg);
- }
- }
- HandleEnd(progress);
- progress = NULL;
- }
- ensure(! ProgressValid(progress));
- }
-